package com.yuneec.droneservice.protocol.analysis;

import java.io.IOException;
import java.io.InputStream;

import com.yuneec.droneservice.tools.RCLog;
import com.yuneec.droneservice.utils.ByteUtils;
import com.yuneec.droneservice.utils.CrcUtils;

public class GreedyPackAnalysis extends Analysis {
    private final int capacity;
    private final byte[] buf;
    private int readPosition;
    private int writePosition;
    private Header header;

    public GreedyPackAnalysis() {
        this(0x10000);
    }

    private GreedyPackAnalysis(int capacity) {
        super();
        this.capacity = capacity;
        this.buf = new byte[capacity];
        header = new Header();
        clear();
    }

    @Override
    public void read(InputStream is) throws IOException {
        if (header.lack > 0) {
            Message msg = header.message;
            int readLen = 0;
            if (msg == null) {
                readLen = is.read(buf, 0, header.lack);
            } else {
                readLen = is.read(msg.body(), msg.offset() + (msg.length() - header.lack), header.lack);
            }
            if (readLen > 0) {
                header.lack -= readLen;
            }
            if (header.lack <= 0) {
                if (msg != null) {
                    msg.sendToTarget();
                }
                header.message = null;
                header.lack = 0;
            }
            return;
        }
        int limit = limit();
        if (limit > 0) {
            writeLen(is.read(buf, writePosition, limit));
        }
        int remain;
        while ((remain = remaining()) > 0) {
            // find the protocol header
            int packStartShift = ByteUtils.simpleFind(buf, readPosition, remain, PROTOCOL_HEADER);
            if (packStartShift < 0) {
                if (buf[writePosition - 1] == PROTOCOL_HEADER_FLAG) {
                    clear();
                    writePosition = 1;
                    buf[0] = PROTOCOL_HEADER_FLAG;
                } else {
                    clear();
                }
                return;
            }
            if (packStartShift > 0) {
                readLen(packStartShift);
            }
            remain = remaining();
            if (remain < 8) {
                System.arraycopy(buf, readPosition, buf, 0, remain);
                clear();
                writePosition = remain;
                return;
            }

            // check crc for header
            byte crc = buf[readPosition + 7];
            byte srcCrc = CrcUtils.crc8Table(buf, readPosition, 7);
            if (crc != srcCrc) {
                RCLog.d("crc error!");
                readLen(1);
                continue;
            }
            int length = getUShort(buf, readPosition + 2);
            int id = getUShort(buf, readPosition + 4);
            byte retain = buf[readPosition + 6];
            readLen(8);
            remain = remaining();
            if (remain >= length) {
                sendMsg(id, length, retain);
            } else {
                fillHalfPack(id, length, retain, remain);
            }
        }
        clear();
    }

    private void sendMsg(int id, int length, byte retain) {
        MessageHandler handler = getMessageHandler(id);
        if (handler != null) {
            Message msg = obtainMessage();
            msg.init(id, length, retain);
            System.arraycopy(buf, readPosition, msg.body(), msg.offset(), length);
            handler.handle(msg);
        }
        readLen(length);
    }

    private void fillHalfPack(int id, int length, byte retain, int readLen) {
        MessageHandler handler = getMessageHandler(id);
        header.lack = length - readLen;
        if (handler != null) {
            Message msg = obtainMessage();
            msg.init(id, length, retain);
            System.arraycopy(buf, readPosition, msg.body(), msg.offset(), readLen);
            header.message = msg;
            msg.setTarget(handler);
        }
        readLen(readLen);
    }

    private void readLen(int len) {
        if (len > 0) {
            readPosition += len;
        }
    }

    private void writeLen(int len) {
        if (len > 0) {
            writePosition += len;
        }
    }

    public int getUShort(byte[] src, int off) {
        if (src.length - off < 2) {
            throw new ArrayIndexOutOfBoundsException();
        }
        byte iLow = src[off];
        byte iHigh = src[off + 1];
        return ((iHigh & 0xFF) << 8) | (0xFF & iLow);
    }

    @Override
    public void clear() {
        this.readPosition = 0;
        this.writePosition = 0;
    }

    public int limit() {
        return capacity - writePosition;
    }

    public int remaining() {
        return writePosition - readPosition;
    }

    private static class Header {
        Message message;
        int lack;
    }
}
